package com.googlecode.monitoring.dao;

import com.googlecode.monitoring.model.*;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.springframework.core.io.Resource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.*;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


@Service(value = "xmlUtils")
public class XmlUtils {
    private Logger log = Logger.getLogger(XmlUtils.class);
    public static final String dateTimeFormat = "dd.MM.yyyy HH:mm:ss";

    /**
     * @param node XML node to build Person from
     * @return Person
     */
    public Person buildPerson(Node node) {
        if (node != null) {
			String fname = null, sname = null, email = null;
			if (node.getAttributes().getNamedItem("fname") != null) {
				fname =node.getAttributes().getNamedItem("fname").getNodeValue();
			}
			if (node.getAttributes().getNamedItem("sname") != null) {
				sname = node.getAttributes().getNamedItem("sname").getNodeValue();
			}
            if (node.getAttributes().getNamedItem("email") != null) {
                email = node.getAttributes().getNamedItem("email").getNodeValue();
            }

            return new Person(fname,sname,email);
        }
        return null;
    }

    /**
     * @param node XML node to build EventTime from
     * @return EventTime
     */
    public EventTime buildEventTime(Node node) {
        if (node != null) {
            EventTime eventTime = new EventTime();
//            if (node.getAttributes().getNamedItem("type") != null) {
//                eventTime.setType(buildEventTimeType(node));
//            }
            if (node.getAttributes().getNamedItem("cron") != null) {
                eventTime.setCron(node.getAttributes().getNamedItem("cron").getNodeValue());
            }
            return eventTime;
        }
        return null;
    }

    public Document getDocumentFromResource(Resource xml) throws Exception {
        return getDocumentFromResource(xml, false);
    }

    public Document getDocumentFromResource(Resource xml, boolean create) throws Exception {
        if (create && !xml.exists()) {
            //throw new DataDaoException(xmlResource.getFile().getAbsolutePath() + " is not exist ");
            createDoc(xml);
        }
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document doc = db.parse(xml.getInputStream());
        doc.normalizeDocument();
        return doc;
    }

    public void createDoc(Resource xml) throws Exception {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document doc = db.newDocument();
        Element root = doc.createElement("document");
        doc.appendChild(root);
        saveDocument(doc, xml);
    }

    public Document createDoc() throws Exception {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document doc = db.newDocument();
        Element root = doc.createElement("document");
        doc.appendChild(root);
        return doc;
    }

    public synchronized void saveDocument(Document doc, Resource xml) throws Exception {
        TransformerFactory tFact = TransformerFactory.newInstance();
        Transformer trans = tFact.newTransformer();
        FileWriter writer = new FileWriter(xml.getFile());
        StreamResult result = new StreamResult(writer);
        DOMSource source = new DOMSource(doc);
        trans.transform(source, result);
        writer.close();
    }

    public StateName buildState(Node node) {
        if (node != null) {
            String nodeValue = node.getAttributes().getNamedItem("name").getNodeValue();
            return com.googlecode.monitoring.model.StateName.buildFromString(nodeValue);
        }
        return null;
    }

    public AlertText getAlertText(Document doc) throws XPathExpressionException {
        XPath xpath = XPathFactory.newInstance().newXPath();
        XPathExpression expr = xpath.compile("document/alertText/*/text()");

        Object result = expr.evaluate(doc, XPathConstants.NODESET);
        NodeList nodes = (NodeList) result;
        AlertText alertText = new AlertText();
        for (int i = 0; i < nodes.getLength(); i++) {
            Node node = nodes.item(i);
            if (node.getParentNode().getNodeName().equals("subject")) {
                alertText.setSubject(node.getNodeValue());
            } else if (node.getParentNode().getNodeName().equals("header")) {
                alertText.setHeader(node.getNodeValue());
            } else if (node.getParentNode().getNodeName().equals("main")) {
                alertText.setMain(node.getNodeValue());
            } else if (node.getParentNode().getNodeName().equals("footer")) {
                alertText.setFooter(node.getNodeValue());
            }
        }
        return alertText;
    }

    public List<String[]> getPlaceHolders(String text) {
        Pattern pattern = Pattern.compile("\\{(\\w)+(\\.(\\w)+)*}");
        Matcher matcher = pattern.matcher(text);
        List<String[]> placeholders = new ArrayList<String[]>();
        while (matcher.find()) {
            String s = matcher.group();
            placeholders.add(s.substring(1, s.length() - 1).split("\\."));
        }
        return placeholders;
    }

    public Set<Person> buildPersons(NodeList personNodeList) {
        if (personNodeList != null && personNodeList.getLength() > 0) {
            Set<Person> plist = new HashSet<Person>(personNodeList.getLength());

            for (int i = 0; i < personNodeList.getLength(); i++) {
                plist.add(buildPerson(personNodeList.item(i)));
            }

            return plist;
        } else {
            return new HashSet<Person>();
        }
    }

    public Set<Person> mergePersons(NodeList personNodeList, Set<Person> alertPersons) {
        Set<Person> plist = buildPersons(personNodeList);

        if (alertPersons != null) {
            plist.addAll(alertPersons);
        }

        return plist;
    }

    public DateTime getNextCheckTime(ItemToCheck itemToCheck, SchedulerFactoryBean schedulerFactoryBean) throws SchedulerException {
        Trigger trigger = schedulerFactoryBean.getScheduler().getTrigger(itemToCheck.getId() + "trigger", itemToCheck.getId() + "Group");
        log.debug(trigger);
        java.util.Date date = trigger.getNextFireTime();
        if (date != null)
            return new DateTime(trigger.getNextFireTime());
        else return null;
    }

    public DateTime buildCheckTime(Node node) {
        if (node != null) {
            String nodeValue = node.getAttributes().getNamedItem("value").getNodeValue();
            if(!nodeValue.isEmpty())
                return DateTimeFormat.forPattern(dateTimeFormat).parseDateTime(nodeValue);
        }
        return null;
    }

    public String replacePlaceHoldersInItem(ItemToCheck item, String text, Document doc, String[] p) throws XPathExpressionException {
        XPath xpath = XPathFactory.newInstance().newXPath();
        XPathExpression exprChildOfItem = xpath.compile("document/item[@id='" + item.getId() + "']/" + p[1]); // get any child of item node
        Node childOfItem = (Node) exprChildOfItem.evaluate(doc, XPathConstants.NODE);
        XPathExpression exprAttr = xpath.compile("attribute::" + p[2]);
        String attributeValue = (String) exprAttr.evaluate(childOfItem, XPathConstants.STRING);
        StringBuilder pattern = new StringBuilder();
        pattern.append("\\{item");
        for (int i = 1; i < p.length; i++) {
            pattern.append("\\." + p[i]);
        }
        pattern.append("\\}");
        text = text.replaceAll(pattern.toString(), attributeValue);
        return text;
    }

    public String replacePlaceHoldersInPerson(Person person, String text, Document doc, String s) throws XPathExpressionException {
        XPath xpath = XPathFactory.newInstance().newXPath();
        XPathExpression expr = xpath.compile("//person[@sname='" + person.getSname() + "']");
        Object result = expr.evaluate(doc, XPathConstants.NODE);
        Node nodePerson = (Node) result;
        expr = xpath.compile("attribute::" + s);
        result = expr.evaluate(nodePerson, XPathConstants.STRING);
        text = text.replaceAll("\\{person\\." + s + "}", result.toString());
        return text;
    }

    public void fillStateAndTimeToItem(Element xmlItemElmnt, ItemToCheck item) {
        Node stateNode = xmlItemElmnt.getElementsByTagName("state").item(0);
        item.setCurrentStateName(buildState(stateNode));
        Node nextCheckTimeNode = xmlItemElmnt.getElementsByTagName("nextCheckTime").item(0);
        item.setNextCheckTime(buildCheckTime(nextCheckTimeNode));
        Node lastCheckTimeNode = xmlItemElmnt.getElementsByTagName("lastCheckTime").item(0);
        DateTime lastCheckTime =  buildCheckTime(lastCheckTimeNode);
        item.setLastCheckTime(lastCheckTime);
    }

    public List<Element> buildPersonNodeList(Document doc, ItemToCheck dbItem) {
        List<Element> personElList = new ArrayList<Element>();
        if (dbItem.getResponsiblePersons() != null && dbItem.getResponsiblePersons().size() > 0) {
            for (Person person : dbItem.getResponsiblePersons()) {
                Element personEl = doc.createElement("person");
                personEl.setAttribute("email", person.getEmail());
                personEl.setAttribute("fname", person.getFname());
                personEl.setAttribute("sname", person.getSname());
                personElList.add(personEl);
            }
        }
        return personElList;
    }

}
